/* main.c */
#include "app_inc.h"

/*******************************************************************************
 * Definitions
 ******************************************************************************/
#define APP_DMA_CAMERA_CHANNEL_IDX   29U

/*******************************************************************************
 * Variables
 ******************************************************************************/
char gAppPrintfBuffer[256];
volatile uint32_t gAppCameraVsyncStartCounter;
volatile uint32_t gAppCameraVsyncEndCounter;
volatile uint32_t gAppCameraHrefStartCounter;
volatile uint32_t gAppCameraHrefEndCounter;
volatile uint32_t gAppCameraPclkStartCounter;

volatile uint32_t gAppCameraLineIndex; /* 当前DMA搬运的是摄像头采样的第几行 */
volatile bool bAppCameraStarted; /* 从VSYNC START开始捕获像素，跳过无效像素 */

/* 存放摄像头像素 */
#define APP_CAMERA_PIXELS_PER_LINE 640U
#define APP_CAMERA_LINES_PER_FRAME 240U /* OV7620摄像头是各行采样, 两个采样帧交叠才能重载一幅原始图像 */
volatile uint8_t gAppCameraData[APP_CAMERA_LINES_PER_FRAME*APP_CAMERA_PIXELS_PER_LINE];

/*******************************************************************************
 * Prototypes
 ******************************************************************************/
void App_BootClockRUN(void);
void App_Tick_PeriodicalCallback(void);

void Camera_Init(void);
void Camera_StartCapture(void);
void Camera_StopCapture(void);
/*******************************************************************************
 * Code
 ******************************************************************************/
/*!
 * @brief Main function
 */
int main(void)
{
    /* Init board hardware. */
    BOARD_InitBootPins();
    BOARD_BootClockRUN();
    App_BootClockRUN(); /* Enable the clocks additionally not in BOARD_BootClockRUN(). */

    /* Initialize debug console. */
    BOARD_InitDebugConsole(); /* Flexcomm0 - UART0, 115200. */

    PRINTF("\r\nApp_DMA_Camera Demo.\r\n");
    PRINTF("build time: %s, %s\r\n", __TIME__, __DATE__);

    OLED_Init();
    //OLED_PutStrP6x8(0U, 0U, "HelloWorld. 1234");
    OLED_PrintReset();
    OLED_PrintStr("Camera Demo.\r\n");

    Tick_Init(200UL); /* 5ms per tick. */
    BTN_Init();
    Tick_InstallPeriodicalCallback(1U, App_Tick_PeriodicalCallback);

    Camera_Init();
    Camera_StartCapture();

    OLED_PrintStr("Capture DONE.\r\n");
    BTN_WaitBlocking(0U); /* 等待KEY0按下并松开 */

    while (1)
    {
        OLED_ClearScreen(0U);
        OLED_PrintReset();
        sprintf(gAppPrintfBuffer, "VSYNC START: %d\r\n"
                                  "VSYNC END  : %d\r\n"
                                  "HREF  START: %d\r\n"
                                  "HREF  END  : %d\r\n"
                                  "PCLK  START: %d\r\n" ,
            gAppCameraVsyncStartCounter,
            gAppCameraVsyncEndCounter,
            gAppCameraHrefStartCounter,
            gAppCameraHrefEndCounter,
            gAppCameraPclkStartCounter
            );
        OLED_PrintStr(gAppPrintfBuffer);
        BTN_WaitBlocking(0U); /* 等待KEY0按下并松开 */
    }
}

/* 启用在BOARD_BootClockRUN()函数中未启用的时钟 */
void App_BootClockRUN(void)
{
    CLOCK_EnableClock(kCLOCK_Gpio0);
    CLOCK_EnableClock(kCLOCK_Gpio1);
    CLOCK_EnableClock(kCLOCK_InputMux);
    CLOCK_EnableClock(kCLOCK_Sct0);
    CLOCK_EnableClock(kCLOCK_Dma);
}

/* 由Systick周期调用的回调函数 */
void App_Tick_PeriodicalCallback(void)
{
    BTN_WaitBlockingHook();
}

#define APP_SCT_EVENT_VSYNC_START 0U /* IN0下降沿 */
#define APP_SCT_EVENT_VSYNC_END   1U /* IN0上升沿 */
#define APP_SCT_EVENT_HREF_START  2U /* IN1上升沿 */
#define APP_SCT_EVENT_HREF_END    3U /* IN1下降沿 */
#define APP_SCT_EVENT_PCLK_START  4U /* IN2上升沿 */

#define APP_SCT_STATE_WAIT_NEW_FRAME  0U
#define APP_SCT_STATE_WAIT_NEW_LINE   1U
#define APP_SCT_STATE_WAIT_NEW_PCLK   2U
#define APP_SCT_STATE_WAIT_NEXT_FRAME 3U

#define APP_SCT_INPUT_LINE_VSYNC  0U
#define APP_SCT_INPUT_LINE_HREF   1U
#define APP_SCT_INPUT_LINE_PCLK   2U

void App_SCT_Configuration(void)
{
    gAppCameraVsyncStartCounter = 0U;
    gAppCameraVsyncEndCounter = 0U;
    gAppCameraHrefStartCounter = 0U;
    gAppCameraHrefEndCounter = 0U;
    gAppCameraPclkStartCounter = 0U;
    bAppCameraStarted = false;

    RESET_PeripheralReset(kSCT0_RST_SHIFT_RSTn);

    /*
      Camera   IO         PinMux      SCT line
    * VSYNC -> PIO0_13 -> SCT0_GPI0 -> IN0
    * HREF  -> PIO0_14 -> SCT0_GPI1 -> IN1
    * PCLK  -> PIO0_17 -> SCT0_GPI7 -> IN2
    */
    INPUTMUX_AttachSignal(INPUTMUX, 0U, kINPUTMUX_SctGpi0ToSct0); /* IN0. */
    INPUTMUX_AttachSignal(INPUTMUX, 1U, kINPUTMUX_SctGpi1ToSct0); /* IN1. */
    INPUTMUX_AttachSignal(INPUTMUX, 2U, kINPUTMUX_SctGpi7ToSct0); /* IN2. */

    /* 在Camera的case中仅使用STATE_L */
    SCT0->CONFIG = SCT_CONFIG_UNIFY(0) /* 双定时器模式 */
                 | SCT_CONFIG_CLKMODE(0) /* 使用System Clock驱动整个SCT */
                 | SCT_CONFIG_INSYNC(0x3FF) /* 启用同步模式，SCT本身的时钟主频比较快，为220MHz */
                 ;


    SCT0->STATE = SCT_STATE_STATE_L(0U); /* 还原为State0 */

    /* 设定在不同状态下可以使能的事件 */
    /* APP_SCT_EVENT_VSYNC_START事件仅在如下状态下被需要：
     * - APP_SCT_STATE_WAIT_NEW_FRAME
     * - APP_SCT_STATE_WAIT_NEXT_FRAME (optional)
     */
    SCT0->EVENT[APP_SCT_EVENT_VSYNC_START].STATE = (1U << APP_SCT_STATE_WAIT_NEW_FRAME )
                                                 | (1U << APP_SCT_STATE_WAIT_NEXT_FRAME)
                                                 ;
    /* APP_SCT_EVENT_HREF_START事件仅在如下状态被需要：
     * - APP_SCT_STATE_WAIT_NEW_LINE
     */
    SCT0->EVENT[APP_SCT_EVENT_HREF_START].STATE  = (1U << APP_SCT_STATE_WAIT_NEW_LINE  )
                                                 ;
    SCT0->EVENT[APP_SCT_EVENT_PCLK_START].STATE  = (1U << APP_SCT_STATE_WAIT_NEW_PCLK  )
                                                 ;
    /* 在任何状态下捕获APP_SCT_EVENT_VSYNC_END都将切换回APP_SCT_STATE_WAIT_NEW_FRAME状态
     * 只是考虑开始捕获摄像头同步信号时，通信帧不完整。
     * 通过捕获VSYNC结束事件初始化整个状态机
     */
    SCT0->EVENT[APP_SCT_EVENT_VSYNC_END  ].STATE = (1U << APP_SCT_STATE_WAIT_NEW_FRAME )
                                                 | (1U << APP_SCT_STATE_WAIT_NEW_LINE  )
                                                 | (1U << APP_SCT_STATE_WAIT_NEW_PCLK  )
                                                 | (1U << APP_SCT_STATE_WAIT_NEXT_FRAME)
                                                 ;
    /* 在任何状态下捕获APP_SCT_EVENT_VSYNC_END都将切换回APP_SCT_STATE_WAIT_NEW_FRAME状态 */
    SCT0->EVENT[APP_SCT_EVENT_HREF_END  ].STATE  = //(1U << APP_SCT_STATE_WAIT_NEW_FRAME )
                                                   //(1U << APP_SCT_STATE_WAIT_NEW_LINE  )
                                                   (1U << APP_SCT_STATE_WAIT_NEW_PCLK  )
                                                 //| (1U << APP_SCT_STATE_WAIT_NEXT_FRAME)
                                                 ;

    /* 设定每个事件的操作 */
    /* VSYNC开始事件：
     * - APP_SCT_INPUT_LINE_VSYNC输入信号线下降沿触发事件发生
     * - 事件发生后切换到状态APP_SCT_STATE_WAIT_NEW_LINE，准备接收新的一行
     */
    SCT0->EVENT[APP_SCT_EVENT_VSYNC_START].CTRL = SCT_EVENT_CTRL_MATCHSEL(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_HEVENT(0)   /* 仅使用State_L状态机 */
                                                | SCT_EVENT_CTRL_OUTSEL(0)   /* 接受输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOSEL(APP_SCT_INPUT_LINE_VSYNC) /* 接受VSYNC输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOCOND(2) /* 触发时机为输入下降沿 */
                                                | SCT_EVENT_CTRL_COMBMODE(2) /* 仅使用IO引脚产生的触发信号，而不考虑定时器系统的触发条件 */
                                                | SCT_EVENT_CTRL_STATELD(1) /* 操作状态机的方式：直接载入新值 */
                                                | SCT_EVENT_CTRL_STATEV(APP_SCT_STATE_WAIT_NEW_LINE) /* 事件发生后载入新的状态值 */
                                                | SCT_EVENT_CTRL_MATCHMEM(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_DIRECTION(0) /* 此配置无效 */
                                                ;
    /* HREF开始事件：
     * - APP_SCT_INPUT_LINE_HREF输入信号线上升沿触发事件发生
     * - 事件发生后切换到状态APP_SCT_STATE_WAIT_NEW_PCLK，准备接收新的像素点数据
     */
    SCT0->EVENT[APP_SCT_EVENT_HREF_START ].CTRL = SCT_EVENT_CTRL_MATCHSEL(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_HEVENT(0)   /* 仅使用State_L状态机 */
                                                | SCT_EVENT_CTRL_OUTSEL(0)   /* 接受输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOSEL(APP_SCT_INPUT_LINE_HREF) /* 接受HREF输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOCOND(1) /* 触发时机为输入上升沿 */
                                                | SCT_EVENT_CTRL_COMBMODE(2) /* 仅使用IO引脚产生的触发信号，而不考虑定时器系统的触发条件 */
                                                | SCT_EVENT_CTRL_STATELD(1) /* 操作状态机的方式：直接载入新值 */
                                                | SCT_EVENT_CTRL_STATEV(APP_SCT_STATE_WAIT_NEW_PCLK) /* 事件发生后载入新的状态值 */
                                                | SCT_EVENT_CTRL_MATCHMEM(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_DIRECTION(0) /* 此配置无效 */
                                                ;
    /* PCLK开始事件：
     * - APP_SCT_INPUT_LINE_PCLK输入信号线上升沿触发事件发生
     * - 事件发生后切换到状态APP_SCT_STATE_WAIT_NEW_PCLK（本状态），准备接收新的像素点数据
     */
    SCT0->EVENT[APP_SCT_EVENT_PCLK_START ].CTRL = SCT_EVENT_CTRL_MATCHSEL(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_HEVENT(0)   /* 仅使用State_L状态机 */
                                                | SCT_EVENT_CTRL_OUTSEL(0)   /* 接受输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOSEL(APP_SCT_INPUT_LINE_PCLK) /* 接受HREF输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOCOND(1) /* 触发时机为输入上升沿 */
                                                | SCT_EVENT_CTRL_COMBMODE(2) /* 仅使用IO引脚产生的触发信号，而不考虑定时器系统的触发条件 */
                                                | SCT_EVENT_CTRL_STATELD(1) /* 操作状态机的方式：直接载入新值 */
                                                | SCT_EVENT_CTRL_STATEV(APP_SCT_STATE_WAIT_NEW_PCLK) /* 事件发生后载入新的状态值 */
                                                | SCT_EVENT_CTRL_MATCHMEM(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_DIRECTION(0) /* 此配置无效 */
                                                ;
    /* HREF结束事件：
     * - APP_SCT_INPUT_LINE_HREF输入信号下降沿触发事件发生
     * - 事件发生后切换到状态APP_SCT_STATE_WAIT_NEW_LINE，准备开始接收下一行数据
     */
    SCT0->EVENT[APP_SCT_EVENT_HREF_END   ].CTRL = SCT_EVENT_CTRL_MATCHSEL(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_HEVENT(0)   /* 仅使用State_L状态机 */
                                                | SCT_EVENT_CTRL_OUTSEL(0)   /* 接受输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOSEL(APP_SCT_INPUT_LINE_HREF) /* 接受HREF输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOCOND(2) /* 触发时机为输入下降沿 */
                                                | SCT_EVENT_CTRL_COMBMODE(2) /* 仅使用IO引脚产生的触发信号，而不考虑定时器系统的触发条件 */
                                                | SCT_EVENT_CTRL_STATELD(1) /* 操作状态机的方式：直接载入新值 */
                                                | SCT_EVENT_CTRL_STATEV(APP_SCT_STATE_WAIT_NEW_LINE) /* 事件发生后载入新的状态值 */
                                                | SCT_EVENT_CTRL_MATCHMEM(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_DIRECTION(0) /* 此配置无效 */
                                                ;
    /* VSYNC结束事件：
     * - APP_SCT_INPUT_LINE_VSYNC输入信号上升沿触发事件发生
     * - 事件发生后切换到状态APP_SCT_STATE_WAIT_NEW_FRAME，准备开始接收下一帧数据
     */
    SCT0->EVENT[APP_SCT_EVENT_VSYNC_END  ].CTRL = SCT_EVENT_CTRL_MATCHSEL(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_HEVENT(0)   /* 仅使用State_L状态机 */
                                                | SCT_EVENT_CTRL_OUTSEL(0)   /* 接受输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOSEL(APP_SCT_INPUT_LINE_VSYNC) /* 接受VSYNC输入引脚的触发 */
                                                | SCT_EVENT_CTRL_IOCOND(1) /* 触发时机为输入上升沿 */
                                                | SCT_EVENT_CTRL_COMBMODE(2) /* 仅使用IO引脚产生的触发信号，而不考虑定时器系统的触发条件 */
                                                | SCT_EVENT_CTRL_STATELD(1) /* 操作状态机的方式：直接载入新值 */
                                                | SCT_EVENT_CTRL_STATEV(APP_SCT_STATE_WAIT_NEW_FRAME) /* 事件发生后载入新的状态值 */
                                                | SCT_EVENT_CTRL_MATCHMEM(0) /* 此配置无效 */
                                                | SCT_EVENT_CTRL_DIRECTION(0) /* 此配置无效 */
                                                ;
    /* 设定DMA0的触发信号 */
    //SCT0->DMA0REQUEST = SCT_DMA0REQUEST_DEV_0(1U << APP_SCT_EVENT_PCLK_START); /* PCLK开始事件可以触发DMA */

    /* 设定可以触发中断的事件 */
    SCT0->EVEN = (1U << APP_SCT_EVENT_VSYNC_START) /* 对场次进行计数 */
               | (1U << APP_SCT_EVENT_VSYNC_END  )
               //| (1U << APP_SCT_EVENT_HREF_START )
               | (1U << APP_SCT_EVENT_HREF_END   )  /* 重新装载描述符 */
               //| (1U << APP_SCT_EVENT_PCLK_START )
               ;
    NVIC_EnableIRQ(SCT0_IRQn);

#if 0
    /* 启动SCT */
    SCT0->EVFLAG = (uint32_t)(-1); /* 清标志位 */
    SCT0->CTRL &= ~SCT_CTRL_HALT_L_MASK;
#endif
}


/* 链式传输描述符内存空间要求16字节对齐 */
uint32_t gAppDmaTransferDescripterBuffer[sizeof(DMA_TransferDescriptor_T) * 20U + 16U];
DMA_TransferDescriptor_T *gDmaTransferDescriptorBase; /* 16字节对齐之后的内存空间 */
volatile uint32_t gAppDmaXfercfg; /* 共享的传输描述符配置字 */

void App_DMA_Configuration(void)
{
    /* 创建零星传输描述符 */
    gDmaTransferDescriptorBase = (DMA_TransferDescriptor_T *)((((uint32_t)gAppDmaTransferDescripterBuffer)+16U) & ~(0xF));

    DMA_Init(DMA0);

    /* 激活NVIC中断 */
    DMA_ClearFlagsOnInterruptA(DMA0, (uint32_t)(-1)); /* 清标志位 */
    DMA_ClearFlagsOnInterruptB(DMA0, (uint32_t)(-1));
    DMA_EnableChannelsInterrupt(DMA0, (1U << APP_DMA_CAMERA_CHANNEL_IDX), true); /* 开Cam传输DMA中断 */
    NVIC_EnableIRQ(DMA0_IRQn);

    /* 设定SCT0可以产生DMA0的触发信号 */
    SCT0->DMA0REQUEST = SCT_DMA0REQUEST_DEV_0(1U << APP_SCT_EVENT_PCLK_START); /* PCLK开始事件可以触发DMA */
    /* 在INUPTMUX中配置触发连接 */
    INPUTMUX_AttachSignal(INPUTMUX, APP_DMA_CAMERA_CHANNEL_IDX, kINPUTMUX_Sct0DmaReq0ToDma); /* 将SCT0的DMA触发线0连接到DMA0通道上 */


}

/* SCT中断服务程序入口，仅用于调试 */
void SCT0_IRQHandler(void)
{
    uint32_t eventFlags = SCT0->EVFLAG;

    /* Camera数据帧开始 */
    if (0U != (eventFlags & (1U << APP_SCT_EVENT_VSYNC_START)))
    {
        bAppCameraStarted = true; /* 标记已经开始捕获有效图像帧 */
        gAppCameraVsyncStartCounter++;
        gAppCameraLineIndex = 0U; /* 初始化第一帧 */
    }
    /* Camera数据帧结束 */
    if (0U != (eventFlags & (1U << APP_SCT_EVENT_VSYNC_END)))
    {
        gAppCameraVsyncEndCounter++;
        if (bAppCameraStarted)
        {
            Camera_StopCapture(); /* 每次StartCapture之后仅捕获一帧图像即刻停止 */
        }
        bAppCameraStarted = false;
    }
    /* 一行数据传输结束 */
    if (0U != (eventFlags & (1U << APP_SCT_EVENT_HREF_END)))
    {
        gAppCameraHrefEndCounter++;
//        gAppCameraLineIndex++;
//        if (bAppCameraStarted)
//        {
//            Camera_StopCapture(); /* 每次StartCapture之后仅捕获一帧图像即刻停止 */
//        }
    }
    SCT0->EVFLAG = eventFlags;
}

/* DMA传输完成中断 */
volatile bool bDmaXferDone;
volatile uint32_t gAppCameraDataIndexEnd; /* 用加法代替乘法，提高CPU的执行速度 */
volatile bool bAppCameraDmaXferDone;
void DMA0_IRQHandler(void)
{
    uint32_t flags = DMA_GetChannelsOnInterruptA(DMA0);

    if (0U != (flags & (1U << APP_DMA_CAMERA_CHANNEL_IDX)))
    {
        /* 自动装载下一个传输描述符 */
        gAppCameraLineIndex++;
        gAppCameraDataIndexEnd += APP_CAMERA_PIXELS_PER_LINE; /* 用加法代替乘法，减少中断服务程序执行时间 */

        if (gAppCameraLineIndex >= APP_CAMERA_LINES_PER_FRAME)
        {
            DMA_EnableChannels(DMA0, (1U<<APP_DMA_CAMERA_CHANNEL_IDX), false);
            bAppCameraDmaXferDone = true;
        }
        else
        {
            /* 配置首行的传输描述符 */
            gDmaTransferDescriptorBase[0].TransferConfigWord = gAppDmaXfercfg; /* 预先配置好传输描述符配置字 */
            gDmaTransferDescriptorBase[0].SourceEndAddrress = 3U + (uint32_t)&(GPIO->PIN[1]); /* PIO1端口中高8位的地址 */
            //gDmaTransferDescriptorBase[0].SourceEndAddrress = ((uint32_t)&cDmaDummyDataU8); /* 暂时使用假数据 */
            //gDmaTransferDescriptorBase[0].DestEndAddress = (uint32_t)&gAppCameraData[APP_CAMERA_PIXELS_PER_LINE*gAppCameraLineIndex-1U]; /* 配置第一行地址 */
            gDmaTransferDescriptorBase[0].DestEndAddress = gAppCameraDataIndexEnd;
            gDmaTransferDescriptorBase[0].LinkToNext = 0U;
            DMA_SetHeadTransferDescriptor(DMA0, APP_DMA_CAMERA_CHANNEL_IDX, &gDmaTransferDescriptorBase[0]);
        }
        bDmaXferDone = true;
    }
    DMA_ClearFlagsOnInterruptA(DMA0, flags);
}

/* 配置支持摄像头的各个模块 */
void Camera_Init(void)
{
    App_SCT_Configuration();
    App_DMA_Configuration();
}

const uint8_t cDmaDummyDataU8 = 0x55;

/* 开始捕获一幅完整的图片 */
void Camera_StartCapture(void)
{
    DMA_ChannelConfig_T DmaChannelConfigStruct = {0U};
    DMA_TransferConfig_T DmaTransferConfigStruct = {0U};
    DMA_BurstTransferConfig_T DmaBurstTransferConfigStruct;

    gAppCameraLineIndex = 0U;
/* 启用DMA通道 */
    DMA_EnableChannels(DMA0, (1U<<APP_DMA_CAMERA_CHANNEL_IDX), true);

/* 配置传输通道 */
    DmaChannelConfigStruct.EnablePeripheralRequest = false;
    DmaChannelConfigStruct.EnableHardwareTrigger = true; /* Enable hardware trigger. */
    DmaChannelConfigStruct.HardwareTriggerMode = eDMA_HardwareTriggerMode_RisingEdge;
    DmaChannelConfigStruct.ChannelPriority = 0U; /* 最高优先级 */
    /* 启用Burst传输模式 */
    DmaBurstTransferConfigStruct.BurstTransferSizePower = 0U;
    DmaBurstTransferConfigStruct.EnableSourceBurstWrap = false;
    DmaBurstTransferConfigStruct.EnableDestBurstWrap = false;
    DmaChannelConfigStruct.BurstTransferConfig = &DmaBurstTransferConfigStruct;
    DMA_SetChannelConfig(DMA0, APP_DMA_CAMERA_CHANNEL_IDX, &DmaChannelConfigStruct);

/* 准备传输描述符配置字 */
    DmaTransferConfigStruct.EnableValidNow = true;
    DmaTransferConfigStruct.EnableReloadNextDescriptor = false; /* 不自动重新载入新描述符. */
    DmaTransferConfigStruct.EnableTriggerBySoftwareNow = false;
    DmaTransferConfigStruct.EnableAutoClearTrigger = true; /* 在描述符耗尽之后，自动关闭DMA的Trigger. */
    DmaTransferConfigStruct.EnableFlagOnInterruptA = true; /* 启用A中断. */
    DmaTransferConfigStruct.EnableFlagOnInterruptB = false;
    DmaTransferConfigStruct.TransferWidth = eDMA_TransferWidth_8b; /* 8位数据搬运 */
    DmaTransferConfigStruct.TransferSourceAddressIncrease = eDMA_TransferAddressIncrease_NoChange; /* 原始地址为摄像头IO端口地址 */
    DmaTransferConfigStruct.TransferDestAddressIncrease = eDMA_TransferAddressIncrease_1xWidth;
    DmaTransferConfigStruct.TransferCount = APP_CAMERA_PIXELS_PER_LINE; /* 传输长度为一行像素 */
    gAppDmaXfercfg = DMA_CreateTransferConfigWord(&DmaTransferConfigStruct);

/* 配置首行的传输描述符 */
    gDmaTransferDescriptorBase[0].TransferConfigWord = gAppDmaXfercfg; /* 预先配置好传输描述符配置字 */
    gDmaTransferDescriptorBase[0].SourceEndAddrress = 3U + (uint32_t)&(GPIO->PIN[1]); /* PIO1端口中高8位的地址 */
    //gDmaTransferDescriptorBase[0].SourceEndAddrress = ((uint32_t)&cDmaDummyDataU8); /* 暂时使用假数据 */
    //gDmaTransferDescriptorBase[0].DestEndAddress = (uint32_t)&gAppCameraData[APP_CAMERA_PIXELS_PER_LINE-1U]; /* 配置第一行地址 */
    gAppCameraDataIndexEnd = (uint32_t)&gAppCameraData[APP_CAMERA_PIXELS_PER_LINE-1U];
    gDmaTransferDescriptorBase[0].DestEndAddress = gAppCameraDataIndexEnd; /* 此处用加法替换乘法，减少DMA中断服务的执行时间 */
    gDmaTransferDescriptorBase[0].LinkToNext = 0U;
    DMA_SetHeadTransferDescriptor(DMA0, APP_DMA_CAMERA_CHANNEL_IDX, &gDmaTransferDescriptorBase[0]);

    /* 启动SCT的状态机，开始捕获PCLK的触发信号 */
    SCT0->EVFLAG = (uint32_t)(-1); /* 清标志位 */
    SCT0->CTRL &= ~SCT_CTRL_HALT_L_MASK;

    bAppCameraDmaXferDone = false;
    while (!bAppCameraDmaXferDone)
    {}
}

/* 停止捕获 */
void Camera_StopCapture(void)
{
    SCT0->CTRL |= SCT_CTRL_HALT_L_MASK;
}

/* EOF. */
